-module(ezlinker_advisory_plugin).

-include("ezlinker_advisory_plugin.hrl").
-include_lib("emqx/include/emqx.hrl").

-export([ register_metrics/0
        , load/0
        , unload/0
        ]).

-export([ on_client_connected/3
        , on_client_disconnected/4
        ]).

-define(LOG(Level, Format, Args), emqx_logger:Level("ezlinker_advisory_plugin: " ++ Format, Args)).

register_metrics() ->
    [emqx_metrics:new(MetricName) || MetricName <- ['ezlinker_advisory_plugin.client_connected',
                                                    'ezlinker_advisory_plugin.client_disconnected']].

load() ->
    lists:foreach(
      fun({Hook, Fun, Filter}) ->
          load_(Hook, binary_to_atom(Fun, utf8), {Filter})
      end, parse_rule(application:get_env(?APP, hooks, []))).

unload() ->
    lists:foreach(
      fun({Hook, Fun, _Filter}) ->
          unload_(Hook, binary_to_atom(Fun, utf8))
      end, parse_rule(application:get_env(?APP, hooks, []))).


%%--------------------------------------------------------------------
%% Client connected
%%--------------------------------------------------------------------
on_client_connected( #{clientid := ClientId, username := Username, peerhost :=Host } ,  _ConnInfo, _Env) ->
    emqx_metrics:inc('ezlinker_advisory_plugin.client_connected'),
    Params = [
        {action, connected},
        {clientId, ClientId},
        {username, Username},
        {ip, iolist_to_binary(ntoa(Host))}
    ],
    send_http_post("connected", Params),
    ok;

on_client_connected(#{}, _ConnInfo, _Env) ->
    ok.
%% IP corvert
ntoa({0, 0, 0, 0, 0, 16#ffff, AB, CD}) ->
  inet_parse:ntoa({AB bsr 8, AB rem 256, CD bsr 8, CD rem 256});
ntoa(IP) ->
  inet_parse:ntoa(IP).
%%--------------------------------------------------------------------
%% Client disconnected
%%--------------------------------------------------------------------
on_client_disconnected(#{}, auth_failure, _ConnInfo, _Env) ->
    ok;
on_client_disconnected(Client, {shutdown, Reason}, ConnInfo, Env) when is_atom(Reason) ->
    on_client_disconnected(Client, Reason, ConnInfo, Env);
on_client_disconnected(#{clientid := ClientId, username := Username, peerhost :=Host}, Reason, _ConnInfo, _Env)
    when is_atom(Reason) ->
    emqx_metrics:inc('ezlinker_advisory_plugin.client_disconnected'),

    Params = [
              {action, disconnected},
              {clientId, ClientId},
              {username, Username},
              {reason, Reason},
              {ip, iolist_to_binary(ntoa(Host))}
            ],
    send_http_post("disconnected", Params),
    ok;
on_client_disconnected(_, Reason, _ConnInfo, _Env) ->
    ?LOG(error, "Client disconnected, cannot encode reason: ~p", [Reason]),
    ok.

%%--------------------------------------------------------------------
%% Internal functions
%%--------------------------------------------------------------------
parse_rule(Rules) ->
    parse_rule(Rules, []).
parse_rule([], Acc) ->
    lists:reverse(Acc);
parse_rule([{Rule, Conf} | Rules], Acc) ->
    Params = emqx_json:decode(iolist_to_binary(Conf)),
    Action = proplists:get_value(<<"action">>, Params),
    Filter = proplists:get_value(<<"topic">>, Params),
    parse_rule(Rules, [{list_to_atom(Rule), Action, Filter} | Acc]).

load_(Hook, Fun, Params) ->
    case Hook of
        'client.connected'    -> emqx:hook(Hook, fun ?MODULE:Fun/3, [Params]);
        'client.disconnected' -> emqx:hook(Hook, fun ?MODULE:Fun/4, [Params])
    end.

unload_(Hook, Fun) ->
    case Hook of
        'client.connected'    -> emqx:unhook(Hook, fun ?MODULE:Fun/3);
        'client.disconnected' -> emqx:unhook(Hook, fun ?MODULE:Fun/4)
    end.
%%--------------------------------------------------------------------
%% User functions
%%--------------------------------------------------------------------
send_http_post(Api, Params) ->
    Json = emqx_json:encode(Params),
    Url = application:get_env(?APP, destination, "http://localhost:2500/ezlinker/data/"),
    case request(post, {string:concat(Url, Api), [], "application/json", Json}, [{timeout, 5000}], [], 0) of
        {ok, _} ->
        ?LOG(debug, "Post to:[~p], Params:~s success~n", [Url, Json]),
        ok;
        {error, Reason} ->
        ?LOG(error, "HTTP request error: ~p", [Reason]),
        error
    end.
    
request(Method, Req, HTTPOpts, Opts, Times) ->
    %% Resend request, when TCP closed by remotely
    case httpc:request(Method, Req, HTTPOpts, Opts) of
        {error, socket_closed_remotely} when Times < 3 ->
        timer:sleep(trunc(math:pow(10, Times))),
        request(Method, Req, HTTPOpts, Opts, Times + 1);
        Other -> Other
    end.
